/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.remote.socket.internal;

import java.beans.EventHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import nemos.remote.rsa.message.impl.MessageServer;
import nemos.remote.util.RemoteUtil;
import nemos.util.log.NemosLogger;

import org.osgi.framework.BundleContext;

/**
 * SocketMessageServer : The socket implementation of the message server.
 * 
 * @author Romain DUBOIS
 */
public class SocketMessageServer extends MessageServer {

    /** The server host name */
    private final String mHostName;
    /** The server port */
    private final int mPort;

    /** The server socket */
    private ServerSocket mServer;
    /** The listening thread */
    private Thread mServerThread;

    /** The logger */
    private final NemosLogger mLogger;

    /**
     * Constructor
     * 
     * @param pHostName
     *            The local host name
     * @param pPort
     *            The port to bound the server to
     */
    public SocketMessageServer(final String pHostName, final int pPort) {
        super();

        mLogger = new NemosLogger(SocketMessageServer.class);
        mServerThread = new Thread(EventHandler.create(Runnable.class, this, "waitForConnections")); //$NON-NLS-1$

        // Find host name if needed
        if (pHostName == null || pHostName.length() == 0) {
            mHostName = RemoteUtil.findHostName();
        } else {
            mHostName = pHostName;
        }
        mPort = pPort;
    }

    /**
     * Start the server.
     * 
     * @param pContext
     *            The bundle context
     * @throws IOException
     *             If server start failed
     */
    public void start(final BundleContext pContext) throws IOException {
        // Launch the server
        mLogger.debug(SocketMessage.SOCKET_SERVER_START, mHostName, Integer.valueOf(mPort));

        // Open server
        mServer = new ServerSocket(mPort);
        setId(SocketConstants.ID_PREFIX + mHostName + SocketConstants.ID_SEPARATOR + mPort);

        // Wait for connections
        mServerThread.start();

        // End start and register
        super.start(pContext, null);

        mLogger.info(SocketMessage.SOCKET_SERVER_STARTED, mHostName, Integer.valueOf(mPort));
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.remote.rsa.message.impl.MessageServer#stop()
     */
    @Override
    public void stop() {
        mLogger.debug(SocketMessage.SOCKET_SERVER_STOP, getId());

        super.stop();

        if (!mServer.isClosed()) {
            try {
                mServer.close();
            } catch (final IOException e) {
                mLogger.error(SocketMessage.SOCKET_ERROR, e);
            }
        }

        if (mServerThread != null && mServerThread.isAlive()) {
            try {
                mServerThread.join();
            } catch (final InterruptedException e) {
                // Do not wait anymore, continue the close operation
                mLogger.error(SocketMessage.INTERRUPTED, e);
            }
            mServerThread = null;
        }
    }

    /**
     * Wait for connections and register them as clients.
     */
    public void waitForConnections() {

        while (!mServer.isClosed()) {
            try {

                // Listen to input connections
                final Socket lConnection = mServer.accept();

                // Try to create transport
                boolean lConnected = false;
                try {
                    addTransport(new SocketTransport(getId(), lConnection));
                    lConnected = true;
                }

                // Close socket if failed
                finally {
                    if (!lConnected) {
                        lConnection.close();
                    }
                }

            } catch (final IOException e) {
                if (mServer.isClosed()) {
                    mLogger.debug(SocketMessage.SOCKET_SERVER_CLOSED);
                } else {
                    mLogger.error(SocketMessage.SOCKET_ERROR, e);
                }
            }
        }
    }
}
